package com.changgou.oauth.service;

import com.changgou.oauth.util.AuthToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class AuthServiceImpl implements AuthService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${auth.ttl}")
    private long ttl;

    @Override
    public AuthToken applyToken(String clientId, String clientSecret, String username, String password) {

        String servicId = "USER-AUTH";
        //1、获取授权认证服务器主机地址及端口
        ServiceInstance chooseClient = loadBalancerClient.choose(servicId);
        if(chooseClient==null){
            logger.error("{}授权认证服务不存在",servicId);
            throw new RuntimeException("授权认证服务不存在！");
        }

        //2、拼接密码模式用于申请令牌的URL
        String url = chooseClient.getUri() + "/oauth/token";

        //3、定义HTTP请求的请求头参数
        MultiValueMap headers = new LinkedMultiValueMap();
        headers.set("Authorization", getHttpBasic(clientId, clientSecret));

        //4、定义HTTP请求的请求体参数
        MultiValueMap body = new LinkedMultiValueMap();
        body.set("grant_type", "password"); //定义好密码授权模式
        body.set("username", username);
        body.set("password", password);

        //5、创建HTTP请求的实例
        HttpEntity<MultiValueMap> httpEntity = new HttpEntity<>(body, headers);

        restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
                //处理当发生认证失败返回401或402时，不需要报错
                if(response.getRawStatusCode()!=400 && response.getRawStatusCode()!=401){
                    super.handleError(response);
                }
            }
        });

        //6、执行HTTP请求
        ResponseEntity<Map> exchange = restTemplate.exchange(url, HttpMethod.POST, httpEntity, Map.class);


        //7、得到申请令牌的响应结果并处理
        Map map = exchange.getBody();
        if(map==null || map.get("access_token")==null || map.get("refresh_token") ==null || map.get("jti") ==null){
            logger.error("申请令牌失败，参数有误。当前参数clientId{},username:{}",clientId,username);
            throw new RuntimeException("申请令牌失败，请求参数有误！");
        }

        //8、设置令牌相关的数据到OAuthToken中，用于返回到controller
        AuthToken token = new AuthToken();
        token.setAccessToken(String.valueOf(map.get("access_token")));
        token.setRefreshToken(String.valueOf(map.get("refresh_token")));
        token.setJti(String.valueOf(map.get("jti")));

        logger.info("用户：{}，访问令牌：{}，刷新令牌：{}，短令牌：{}" ,username,token.getAccessToken(), token.getRefreshToken() , token.getJti());


        //9、处理redis，保存数据(Redis的String结构：  key:短令牌jti  val:长令牌accessToken )
        //redis的生效日期就代表用户访问令牌的失效日期
        stringRedisTemplate.boundValueOps(token.getJti()).set(token.getAccessToken(), ttl, TimeUnit.SECONDS);


        return token;
    }

    private String getHttpBasic(String clientId,String clientSecret){
        String str = clientId + ":"+ clientSecret;
        return "Basic " + Base64Utils.encodeToString(str.getBytes());
    }
}
